Skip to content

Census the self-test name anchor's accidental matches and pin their cost - #14284

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-13941-selftest-decl-collision
Sep 2, 2026
Merged

Census the self-test name anchor's accidental matches and pin their cost#14284
baozhoutao merged 1 commit into
mainfrom
claude/issue-13941-selftest-decl-collision

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #13941

Triage fixed the dispatch order into the card and it binds: census first, remedy second — the census result SELECTS the remedy, so no shape was chosen before the population was known. This PR reports the census, then ships what the census selected.

1. Premise re-check, on today's origin/main

#14004 (81256734) added ~412 lines to scripts/pm/dispatch-gates.mjs after the card's measurement, so everything below is re-measured rather than recalled. Base of this branch: 812567346.

The card's reading reproduces, and it understates the surface. When the module masks itself, six spans are blanked, not one:

span bytes blanked why
maskSelfTests 649 name matches the anchor — the card's specimen
selfTestOnlyCallables 1,646 name matches the anchor — added by #14004, new since the card
bracedBodyEnd 823 collateral: reachable only from the two above, which the mask treats as self-tests
valueDeclEnd 375 collateral, same
topLevelDecls 721 collateral, same
selfTest 421,024 the genuine entry point — correct

Controls that make this a reading rather than an artifact: extractWatchHints (7,884 non-whitespace chars) and isNonPathNamespace (1,992) survive the mask untouched. The card's named control indexRefusalAccumulators no longer exists on this tree.

"Costs nothing today" still holds, and is now measured rather than asserted: no slash-bearing string literal appears anywhere in the five collision spans (the first one is at L9158, inside the genuine selfTest), and this file's hint set is identical with and without the collision.

2. The census — the ruled first step

209 code-position SELF_TEST_DECL matches over the tracked JS/TS corpus (5,659 files). 187 are the bare selfTest. The remaining 22 carry compound names over 21 distinct spellings — the population the card put a floor of 19/18 on. Classified:

Genuine self-test batteries — 15 (the anchor working)

file name
packages/lint/scripts/check-doc-formula-expressions.mjs specSelfTest
packages/lint/scripts/check-doc-formula-expressions.mjs fieldRuleSelfTest
scripts/check-comment-mask-corpus.mjs runSelfTestCases
scripts/check-doc-authoring.mjs selfTestRule3
scripts/check-doc-authoring.mjs selfTestPackagesProse
scripts/check-durability-degradation-log-level.mjs selfTestReadSeams
scripts/check-platform-checklist.mjs selfTestTrapVocabulary
scripts/check-platform-checklist.mjs selfTestProvisioningUse
scripts/check-platform-checklist.mjs selfTestUnreferencedRecipes
scripts/check-platform-checklist.mjs selfTestMetaCallSpelling
scripts/check-platform-checklist.mjs selfTestSourceLineCitations
scripts/check-regen-pending.mjs fixtureSelfTest
scripts/check-regen-pending.mjs prePushIsArmedSelfTest
scripts/check-regen-pending.mjs decisionTableSelfTest
scripts/check-turbo-task-graph.mjs runSelfTest

Accidental — production code the anchor caught by its name — 7, over 4 files

file name role spells a path today?
scripts/check-self-test-wired.mjs carriesSelfTest exported predicate: does this file carry a self-test? no
scripts/check-self-test-workflow-commands.mjs runSelfTest exported: spawns another script's --self-test no
scripts/check-step-collectors.mjs selfTestTargets exported extractor over a workflow run: body no
scripts/check-step-collectors.mjs selfTestDiscoveries exported extractor, same no
scripts/measure-self-test-floor.mjs selfTestDefs exported extractor over a module source no
scripts/pm/dispatch-gates.mjs selfTestOnlyCallables internal reachability helper no
scripts/pm/dispatch-gates.mjs maskSelfTests the masker itself — the card's specimen no

The class has SEVEN members, not one. They cluster because every one of them is a gate that reasons about self-tests: a tool that finds, spawns, counts or masks other scripts' self-tests names its functions after the thing it handles, and a name-based anchor cannot tell "runs a self-test" from "is one".

Cost, measured per declaration

Each of the 22 was neutralised one at a time (the self-test token replaced inside the identifier, word-anchored) and the file's hints re-extracted, refusing unless the anchored-declaration count dropped by exactly one:

  • All 7 accidental members: zero hint delta. The card's "costs nothing today" holds tree-wide, over a surface 7x the one it measured.
  • 2 genuine members do move hints: fixtureSelfTest drops packages/spec/spec-changes.json, prePushIsArmedSelfTest drops .githooks/pre-push — both fixture paths in scripts/check-regen-pending.mjs, both correctly refused by the mask.

That asymmetry is load-bearing: "no compound-name match may contribute a hint" is false as a blanket invariant. The invariant holds only over the accidental half, and only a classification can name that half.

3. Remedy — and why both of triage's candidates are refuted by the census

Triage's decision table sent a MULTIPLE-member census to "narrowing SELF_TEST_DECL, or a self-scan special case — pick with the measurement in hand". The measurement refutes both:

  • Narrowing the name pattern is impossible, not merely risky. runSelfTest is a genuine entry point in scripts/check-turbo-task-graph.mjs (reached only from that file's --self-test guard) and accidental in scripts/check-self-test-workflow-commands.mjs (exported, called from the gate body at line 264). One spelling, both classes. No predicate over the name separates them, so any narrowing that excludes the accidental one also unmasks a real self-test battery and readmits its fixture paths as hints — trading a silence that costs nothing for the fabricated-lead family this masker exists to refuse.
  • A self-scan special case fixes 2 rows of 7. The other five live in three other files, so the card's own "fixes one instance and leaves the class" objection applies to it too, one file wider — plus the hazard the card names, that the tool's self-scan would differ from every other scan.

What ships is neither shape. The anchor is unchanged, the mask still blanks all 22, no path is special-cased, and nothing is renamed. What changes is that the seven accidental members' cost is measured on every run instead of asserted in a docblock. Silence was the defect; the remedy is noise on the day it stops being free.

This is deliberately remedy-neutral — it forecloses none of the three shapes if triage still wants one. The fork is reported on the card.

4. What this PR contains

One file, scripts/pm/dispatch-gates.mjs (+277 / -7):

  • COMPOUND_ANCHOR_LEDGER — the census above as data: 22 rows of file, name, accidental. Keyed by file plus name because runSelfTest proves a name-keyed ledger cannot hold both verdicts at once.
  • compoundAnchorDecls(source) — every compound-name anchor match at code positions only (comments masked first, string literals excluded via scanSource, so a fixture that builds a module source cannot enter the census).
  • withoutAnchor(source, name) — the counterfactual rename, word-anchored so runSelfTest does not rewrite runSelfTestCases.
  • Three self-test cases, live over the tracked tree (~3.3s):
    1. every compound-name match is classified — an unlisted or vanished one reds;
    2. masking an accidental row still costs no hint — reds and names the hidden hint;
    3. control: at least one genuine row is seen to lose a fixture hint, so a counterfactual that silently failed to rename cannot report "zero everywhere" and read as a pass.
  • Two identity pins that maskSelfTests and selfTestOnlyCallables are in the anchor's own population, so a rename that "fixes" either has to move the ledger row rather than the problem.
  • The stale claim in SELF_TEST_DECL's docblock corrected — it said the collision was one declaration; it is seven.

5. Verification

All at head 169cfb977.

pnpm check:pm-dispatch-gates — the module's own battery, run under the shared verify lock: dispatch-gates self-test: 1179 cases pass., VERDICT command-exit 0 (held 676s). The five new cases are in it, all green.

Ablation, both directions — mutation proved on disk before each reading, restored against HEAD and proved byte-exact by blob hash afterwards:

  • Injected a path literal into maskSelfTests — assertion 2 goes red: scripts/pm/dispatch-gates.mjs::maskSelfTests hides ["packages/ablation-only/src"]. This is the card's exact scenario, made loud.
  • Appended function ablationSelfTestNewcomer() to scripts/check-turbo-task-graph.mjs — assertion 1 goes red: unlisted: scripts/check-turbo-task-graph.mjs::ablationSelfTestNewcomer.
  • Restores verified: git hash-object equals the HEAD blob in both cases, zero residue, git status --porcelain empty.

Derived families (node scripts/pm/dispatch-gates.mjs, 15 matched) all green — exit codes captured before any pipe, verdict lines quoted in the terminal report. check-test-completeness.mjs exits 3 = PREREQUISITE NOT MET locally (it grades a saved turbo log CI tees); recorded as NOT MEASURED, not as a red.

Changeset route: skip-changeset. The diff is one file under scripts/pm/** — PM tooling, publishing nothing from any package — which is the label's closed list. Applied on open per the label's read-back discipline.


Generated by Claude Code

…n their cost

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants